﻿using System;
using System.Threading;
using Accona.Remoting.Multicast;

namespace Accona.Remoting.Remoting
{
    public class RemoteTranceiver : IRepository
    {
        private readonly Guid appId = Guid.NewGuid();
        private readonly IResolver resolver;
        private readonly WaiterQueue waiterQueue;

        private IReceiver announcementReceiver;
        private ISender announcementSender;
        private IReceiver commandReceiver;
        private ISender commandSender;

        private RemoteObjectExecution executor;
        private RemoteObjectProxyFactory factory;
        private IReceiver replyReceiver;
        private ISender replySender;
        private ServiceAnnouncer serviceAnnouncer;
        private ServiceLookup serviceLookup;

        public RemoteTranceiver(IResolver resolver)
        {
            this.resolver = resolver;
            waiterQueue = new WaiterQueue();
        }

        #region IRepository Members

        public T Resolve<T>()
        {
            return factory.CreateProxy<T>();
        }

        #endregion

        public void SetupCommunication()
        {
            commandSender = new MulticastSender("239.12.13.14", 7654, 0);
            commandReceiver = new MulticastReceiver("239.12.13.14", 7654);
            replySender = new MulticastSender("239.12.13.14", 7655, 0);
            replyReceiver = new MulticastReceiver("239.12.13.14", 7655);

            announcementSender = new MulticastSender("239.12.13.14", 7656, 0);
            announcementReceiver = new MulticastReceiver("239.12.13.14", 7656);

            commandReceiver.OnMessage += CommandReceiverOnMessage;
            replyReceiver.OnMessage += ReplyReceiverOnMessage;

            serviceLookup = new ServiceLookup(announcementReceiver, appId);

            executor = new RemoteObjectExecution(appId, resolver);
            factory = new RemoteObjectProxyFactory(appId, waiterQueue, commandSender, serviceLookup);

            serviceAnnouncer = new ServiceAnnouncer(announcementSender, appId);
            serviceAnnouncer.StartAnnounce();

            Thread.Sleep(1000); // Make announcements arrive before continue
        }

        private void ReplyReceiverOnMessage(byte[] data)
        {
            var serializer = new MethodReplySerializer(appId);
            DeserializedObject obj = serializer.Deserialize(data);
            if (obj != null && obj.Value != null)
            {
                waiterQueue.NotifyMessage(obj);
            }
        }

        private void CommandReceiverOnMessage(byte[] data)
        {
            // Message received, execute method
            RemoteObjectExecution.ExecutionResult result = executor.Execute(data);
            if (result != null && result.ResultValue != null && !result.ResultValue.GetType().Equals(typeof (void)))
            {
                // If the method has a result value (not void), send a reply to the client.
                var serializer = new MethodReplySerializer(appId);
                byte[] obj = serializer.Serialize(result.ResultValue,
                                                  result.MethodInvocationInfo.SourceMetaData.ApplicationId,
                                                  result.MethodInvocationInfo.SourceMetaData.CallId);
                replySender.Send(obj);
            }
        }
    }
}